//
//  DeferredLightingView.m
//  DeferredLighting
//
//  Created by Holmes Futrell on 2/8/11.
//  Copyright 2011 __MyCompanyName__. All rights reserved.
//

#import "DeferredLightingView.h"

@interface DeferredLightingView()
- (void)drawHUD:(NSString *)modeString;
-(mat4)getModelview;
@end


enum {
	kPassGBuffer,
	kPassShade
};

enum {
	kFinalBlitModeDepth=0,
	kFinalBlitModeNormal,
	kFinalBlitModeDiffuse,
	kFinalBlitModeAmbient,
	kFinalBlitModeAmbientFiltered,
	kFinalBlitModeColor,
	kNumFinalBlitModes
};

@implementation DeferredLightingView

- (id)initWithFrame:(NSRect)frameRect pixelFormat:(NSOpenGLPixelFormat *)format {

	if ( self = [super initWithFrame: frameRect pixelFormat: format] ) {
		fbo = 0;
		colorTextureID = 0;
		depthTextureID = 0;
		normalTextureID = 0;
		finalBlitMode = kFinalBlitModeAmbient;
		srand(time(NULL));
		
		return self;
	}
	else {
		return nil;
	}
	
}

- (BOOL)loadShaders
{
		
	tangentShader = [[ShaderProgram alloc] initWithVertexShader: [[NSBundle mainBundle] pathForResource:@"tangentspace" ofType:@"vsh"]
												 fragmentShader: [[NSBundle mainBundle] pathForResource:@"tangentspace" ofType:@"fsh"]];
	
	[tangentShader linkProgram];
	
	
	basicShader = [[ShaderProgram alloc] initWithVertexShader: [[NSBundle mainBundle] pathForResource:@"basic" ofType:@"vsh"]
											   fragmentShader: [[NSBundle mainBundle] pathForResource:@"basic" ofType:@"fsh"]];
	[basicShader linkProgram];
	
	blitShader = [[ShaderProgram alloc] initWithVertexShader: [[NSBundle mainBundle] pathForResource:@"blit" ofType:@"vsh"]
											   fragmentShader: [[NSBundle mainBundle] pathForResource:@"blit" ofType:@"fsh"]];
	[blitShader linkProgram];
	
	gammaCorrectShader = [[ShaderProgram alloc] initWithVertexShader: [[NSBundle mainBundle] pathForResource:@"blit" ofType:@"vsh"]
											  fragmentShader: [[NSBundle mainBundle] pathForResource:@"gamma" ofType:@"fsh"]];
	[gammaCorrectShader linkProgram];
	
	
	mixShader = [[ShaderProgram alloc] initWithVertexShader: [[NSBundle mainBundle] pathForResource:@"mix" ofType:@"vsh"]
											  fragmentShader: [[NSBundle mainBundle] pathForResource:@"mix" ofType:@"fsh"]];
	[mixShader linkProgram];
	
	ssdoShader = [[ShaderProgram alloc] initWithVertexShader: [[NSBundle mainBundle] pathForResource:@"blit" ofType:@"vsh"]
											 fragmentShader: [[NSBundle mainBundle] pathForResource:@"ssdo" ofType:@"fsh"]];
	[ssdoShader linkProgram];
	
	bilateralShader = [[ShaderProgram alloc] initWithVertexShader: [[NSBundle mainBundle] pathForResource:@"blit" ofType:@"vsh"]
											  fragmentShader: [[NSBundle mainBundle] pathForResource:@"bilateral" ofType:@"fsh"]];
	[bilateralShader linkProgram];
	
	
	lightBufferShader = [[ShaderProgram alloc] initWithVertexShader: [[NSBundle mainBundle] pathForResource:@"lightbuffer" ofType:@"vsh"]
											  fragmentShader: [[NSBundle mainBundle] pathForResource:@"lightbuffer" ofType:@"fsh"]];
	
	[lightBufferShader linkProgram];
	
	
	visualizeColor = [[ShaderProgram alloc] initWithVertexShader: [[NSBundle mainBundle] pathForResource:@"visualize" ofType:@"vsh"]
						fragmentShader: [[NSBundle mainBundle] pathForResource:@"visualize-color" ofType:@"fsh"]];
	
	[visualizeColor linkProgram];
	
	simpleGenLightbufferShader = [[ShaderProgram alloc] initWithVertexShader: [[NSBundle mainBundle] pathForResource:@"simpleGenLightbuffer" ofType:@"vsh"]
													 fragmentShader: [[NSBundle mainBundle] pathForResource:@"simpleGenLightbuffer" ofType:@"fsh"]];
	
	[simpleGenLightbufferShader linkProgram];
	
    // Get uniform locations.
	
	count = 0;
	fps = 0;
	[NSTimer scheduledTimerWithTimeInterval: 1 target: self selector:@selector(countFrames:) userInfo: nil repeats: YES];
	[NSTimer scheduledTimerWithTimeInterval: 0 target: self selector:@selector(animationTimer:) userInfo: nil repeats: YES];

	rb_check_error_simple();
	
    return TRUE;
}

-(void)animationTimer:(NSTimer *)timer {
	[self setNeedsDisplay: YES];
}

-(void)countFrames:(NSTimer *)timer {
	fps = count;
	count = 0;
}

-(void)visualizeColorWithImageSize:(CGSize)size dstRect:(CGRect)dst {
	
	[visualizeColor useProgram];
	
	mat4 projection = rb_mat4_ortho(0, viewportWidth, 0, viewportHeight, -10, 10);
	[visualizeColor useProgram];
	
	glUniformMatrix4fv([visualizeColor locationForUniform:@"projectionMatrix"], 1, 0, projection.entries);
	
	int i1 = [visualizeColor getLocationForAttribute:@"position_in"];
	int i2 = [visualizeColor getLocationForAttribute:@"uv_in"];

	glUniform1i([visualizeColor locationForUniform:@"sampler"], 0);
	
	rb_check_error_simple();
	
	float xMin = dst.origin.x;
	float xMax = dst.origin.x + dst.size.width;
	float yMin = dst.origin.y;
	float yMax = dst.origin.y + dst.size.height;

	GLfloat texcoords[] = { 0.0, 0.0, 1, 0.0, 1, 1, 1, 1, 0.0, 1, 0.0, 0.0 };
	GLfloat coords[] = { xMin, yMin, xMax, yMin, xMax, yMax, xMax, yMax, xMin, yMax, xMin, yMin };

	glEnableVertexAttribArray(i1);
	glVertexAttribPointer(i1, 2, GL_FLOAT, GL_FALSE, 0, coords); 
	rb_check_error_simple();
	glEnableVertexAttribArray(i2);
	glVertexAttribPointer(i2, 2, GL_FLOAT, GL_FALSE, 0, texcoords); 
	rb_check_error_simple();

	glDrawArrays(GL_TRIANGLES, 0, 6);
	rb_check_error_simple();
	
	glDisableVertexAttribArray(i1);
	glDisableVertexAttribArray(i2);
	rb_check_error_simple();
	
	
}


-(void)drawSquare:(int)positionAttributeLocation {
	
	rb_check_error_simple();
	GLfloat coords[] = { 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0 };
	glEnableVertexAttribArray(positionAttributeLocation);
	rb_check_error_simple();
	glVertexAttribPointer(positionAttributeLocation, 2, GL_FLOAT, GL_FALSE, 0, coords); 
	rb_check_error_simple();
	glDrawArrays(GL_TRIANGLES, 0, 6);
	rb_check_error_simple();	
	glDisableVertexAttribArray(positionAttributeLocation);
	
	rb_check_error_simple();

	
}

-(mat4)getModelview {

	mat4 modelView = rb_mat4_rotate( -3.14159f / 2.0f, 1, 0, 0 );
	modelView = rb_mat4_mul(rb_mat4_rotate( M_PI, 1, 0, 0 ), modelView);
	modelView = rb_mat4_mul(rb_mat4_rotate( rot, 0, 1, 0 ), modelView);
	modelView = rb_mat4_mul(rb_mat4_rotate( 180.0f * M_PI / 180.0f, 1, 0, 0 ), modelView);
	modelView = rb_mat4_mul(rb_mat4_translate(make_vec3(0,0.0,-6.0)), modelView);
	return modelView;
	
}

-(void)drawPass:(int)pass {
			
	mat4 modelView = [self getModelview];
	mat4 projection = rb_mat4_frustum( left, right, bottom, top, zNear, zFar);
	
	//mat4 projection = rb_mat4_perspective( fovyInDegrees, aspect, zNear, zFar);
	
	mat4 modelViewProjection = rb_mat4_mul(projection, modelView);	
			
	if ( pass == kPassGBuffer ) {
						
		glBindFramebuffer(GL_FRAMEBUFFER, fbo);
		
		
		glClearColor(0.5, 0.5, 0.5, 0.0);		
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, normalTextureID, 0);
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTextureID, 0);

		GLuint status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
		if ( status != GL_FRAMEBUFFER_COMPLETE ) {
			NSLog(@"Incomplete framebuffer %s (pass = %s)", rbunny_framebuffer_status_to_string(status), "kPassGBuffer" );
		}				
		
		// Use shader program.
		[tangentShader useProgram];
		
		glUniformMatrix4fv( [tangentShader locationForUniform:@"modelviewMatrix"], 1, 0, modelView.entries);
		glUniformMatrix4fv( [tangentShader locationForUniform:@"modelviewProjectionMatrix"], 1, 0, modelViewProjection.entries);		
		glUniform1f([tangentShader locationForUniform:@"specularPower"],	75.0f);
		glUniform1i([tangentShader locationForUniform:@"normalsSampler"],	0);
						
		glEnable(GL_DEPTH_TEST);	
		glDepthFunc(GL_LEQUAL);

		glDepthMask(GL_TRUE);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		
		[model11 bindVertexArray];
		[model11 draw];	
		[model11 unbindVertexArray];
				
		[model21 bindVertexArray];
		[model21 draw];	
		[model21 unbindVertexArray];
		
		
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, 0);

		
	}
	else if ( pass == kPassShade ) {
		
		glClearColor(0.0, 0.0, 0.0, 1.0);		
		glBindFramebuffer(GL_FRAMEBUFFER, fbo);
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, diffuseTextureID, 0);
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, albedoTextureID, 0);
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTextureID, 0);
		rb_check_error_simple();

		GLenum buffers[2] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1};
		glDrawBuffers(2, buffers);
		rb_check_error_simple();

		GLuint status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
		if ( status != GL_FRAMEBUFFER_COMPLETE ) {
			NSLog(@"Incomplete framebuffer %s (pass = %s)", rbunny_framebuffer_status_to_string(status), "kPassGBuffer" );
		}				
		
		// Use shader program.
		[lightBufferShader useProgram];
		rb_check_error_simple();

		glUniformMatrix4fv( [lightBufferShader locationForUniform:@"modelviewMatrix"], 1, 0, modelView.entries);
		glUniformMatrix4fv( [lightBufferShader locationForUniform:@"modelviewProjectionMatrix"], 1, 0, modelViewProjection.entries);		
		glUniform1i([lightBufferShader locationForUniform:@"textureSampler"],	0);
		glUniform1i([lightBufferShader locationForUniform:@"normalSampler"],	1);
		glUniform2f([lightBufferShader locationForUniform:@"imageSize"], viewportWidth, viewportHeight);

		rb_check_error_simple();


		glActiveTexture(GL_TEXTURE1);
		glBindTexture(GL_TEXTURE_2D, normalTextureID); 
		rb_check_error_simple();

		glEnable(GL_DEPTH_TEST);
		glDepthMask(GL_FALSE);
		glClear(GL_COLOR_BUFFER_BIT);
		rb_check_error_simple();

		
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, texture1.descriptor);
		rb_check_error_simple();
		
		[model12 bindVertexArray];
		[model12 draw];	
		[model12 unbindVertexArray];

		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, texture2.descriptor);
		rb_check_error_simple();
		
		
		[model22 bindVertexArray];
		[model22 draw];	
		[model22 unbindVertexArray];
		
		
		rb_check_error_simple();
				
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, 0, 0);
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
		glDrawBuffer(GL_COLOR_ATTACHMENT0);

		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, 0);
		glActiveTexture(GL_TEXTURE1);
		glBindTexture(GL_TEXTURE_2D, 0);
		glActiveTexture(GL_TEXTURE2);
		glBindTexture(GL_TEXTURE_2D, 0);
		rb_check_error_simple();

		
	}
	
	rb_check_error_simple();
			
	glBindFramebuffer(GL_FRAMEBUFFER, 0);

	
}

-(void)keyDown:(NSEvent *)theEvent {

	
	NSString *characters = [theEvent characters];
	if ([characters length]) {
				
		switch ( [characters characterAtIndex:0] ) {
			case 'x':
				finalBlitMode++;
				finalBlitMode %= kNumFinalBlitModes;
				break;
			case 'z':
				finalBlitMode--;
				if ( finalBlitMode < 0 ) finalBlitMode += kNumFinalBlitModes;
				break;
			case ' ':
				paused = !paused;
				break;
		}
		
	}
	
}

void generateSample( float *dir ) {

	float epsilon1 = drand48();
	float epsilon2 = drand48();
	float lambda = 1.0f;

	float phi = acos(sqrt(epsilon1));
	float theta	= 2.0f * pi * epsilon2;
	
	dir[0] = lambda * cos(theta) * sin(phi);
	dir[1] = lambda * sin(theta) * sin(phi);
	dir[2] = lambda * cos(phi);
		
}

-(void)generateSampleDirections {

	samplePatternSize[0] = 4;
	samplePatternSize[1] = 4;
	
	int numPixels = samplePatternSize[0] * samplePatternSize[1] * NUM_SAMPLES;
	float *samplePattern = (float *)malloc(sizeof(float) * 4 * numPixels);
	
	int i;
	for (i=0; i<numPixels; i++) {
		generateSample( &(samplePattern[4*i]) );
		samplePattern[4*i+3] = 0.0f;
	}
	
	glGenTextures(1, &samplePatternTextureID);
	glBindTexture(GL_TEXTURE_2D, samplePatternTextureID);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F_ARB, samplePatternSize[0] * NUM_SAMPLES, samplePatternSize[1], 0, GL_RGBA, GL_FLOAT, samplePattern);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	
	free(samplePattern);
}

-(void)filterAmbient {

	glClearColor(0.0, 1.0, 0.0, 1.0);		
	glBindFramebuffer(GL_FRAMEBUFFER, fbo);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, filteredAmbientTextureID, 0);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
	
	glDisable(GL_DEPTH_TEST);
	glDepthMask(GL_FALSE);
	glClear(GL_COLOR_BUFFER_BIT);
	
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, depthTextureID);
	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, normalTextureID);
	glActiveTexture(GL_TEXTURE2);
	glBindTexture(GL_TEXTURE_2D, ambientTextureID);
	glActiveTexture(GL_TEXTURE3);

	[bilateralShader useProgram];
	int i = [bilateralShader getLocationForAttribute:@"position_in"];
	rb_check_error_simple();

		
	glUniform1i([bilateralShader locationForUniform:@"depthSampler"], 0);
	rb_check_error_simple();

	glUniform1i([bilateralShader locationForUniform:@"normalSampler"], 1);	
	rb_check_error_simple();

	glUniform1i([bilateralShader locationForUniform:@"ambientSampler"], 2);		
	rb_check_error_simple();

	glUniform2f([bilateralShader locationForUniform:@"invImageSize"], 1.0f / (float)viewportWidth, 1.0f / (float)viewportHeight);
	rb_check_error_simple();

	glUniform1i([bilateralShader locationForUniform:@"filterSize"], 4);
	rb_check_error_simple();

	glUniform1f([bilateralShader locationForUniform:@"variance"], 16);

	rb_check_error_simple();
	
	[self drawSquare: i];
	
	glEnable(GL_DEPTH_TEST);
	glDepthMask(GL_TRUE);
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, 0);
	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, 0);
	glActiveTexture(GL_TEXTURE2);
	glBindTexture(GL_TEXTURE_2D, 0);	
	glActiveTexture(GL_TEXTURE0);
	rb_check_error_simple();
	
}

-(void)generateAmbient {
	
	glClearColor(0.0, 1.0, 0.0, 1.0);		
	glBindFramebuffer(GL_FRAMEBUFFER, fbo);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, ambientTextureID, 0);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
		
	glDisable(GL_DEPTH_TEST);
	glDepthMask(GL_FALSE);
	glClear(GL_COLOR_BUFFER_BIT);
	
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, depthTextureID);
	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, normalTextureID);
	glActiveTexture(GL_TEXTURE2);
	glBindTexture(GL_TEXTURE_2D, diffuseTextureID);
	glActiveTexture(GL_TEXTURE3);
	glBindTexture(GL_TEXTURE_2D, albedoTextureID);
	glActiveTexture(GL_TEXTURE4);
	glBindTexture(GL_TEXTURE_2D, lightmap.descriptor);
	glActiveTexture(GL_TEXTURE5);
	glBindTexture(GL_TEXTURE_2D, samplePatternTextureID);

	[ssdoShader useProgram];
	int i = [ssdoShader getLocationForAttribute:@"position_in"];

	
	
	float		Q1 = 2.0f * zNear / ( right - left );
	float		Q2 = 2.0f * zNear / ( top - bottom );
	float		C  = - (zFar + zNear) / (zFar - zNear);
	float		D  = - (2.0f * zFar * zNear) / (zFar - zNear);	
	
	rb_check_error_simple();

	mat4 modelview = [self getModelview];
	glUniformMatrix4fv( [ssdoShader locationForUniform:@"modelviewMatrix"], 1, 0, modelview.entries);

	glUniform1i([ssdoShader locationForUniform:@"numSamples"], NUM_SAMPLES );	

	
	glUniform1i([ssdoShader locationForUniform:@"depthSampler"], 0);	
	glUniform1i([ssdoShader locationForUniform:@"normalSampler"], 1);	
	glUniform1i([ssdoShader locationForUniform:@"diffuseSampler"], 2);	
	glUniform1i([ssdoShader locationForUniform:@"albedoSampler"], 3);
	glUniform1i([ssdoShader locationForUniform:@"lightmapSampler"], 4);	
	glUniform1i([ssdoShader locationForUniform:@"samplePatternDirections"], 5);	

	glUniform2f([ssdoShader locationForUniform:@"invImageSize"], 1.0f / (float)viewportWidth, 1.0f / (float)viewportHeight);
	glUniform2f([ssdoShader locationForUniform:@"samplePatternSize"], samplePatternSize[0], samplePatternSize[1]);

	glUniform1f([ssdoShader locationForUniform:@"Q1"], Q1);	
	glUniform1f([ssdoShader locationForUniform:@"Q2"], Q2);	
	glUniform1f([ssdoShader locationForUniform:@"C"], C);	
	glUniform1f([ssdoShader locationForUniform:@"D"], D);	
	glUniform1f([ssdoShader locationForUniform:@"rmax"], 0.6f);	

	rb_check_error_simple();

	[self drawSquare: i];
		
	glEnable(GL_DEPTH_TEST);
	glDepthMask(GL_TRUE);
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, 0);
	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, 0);
	glActiveTexture(GL_TEXTURE2);
	glBindTexture(GL_TEXTURE_2D, 0);
	glActiveTexture(GL_TEXTURE3);
	glBindTexture(GL_TEXTURE_2D, 0);
	glActiveTexture(GL_TEXTURE4);
	glBindTexture(GL_TEXTURE_2D, 0);
	glActiveTexture(GL_TEXTURE5);
	glBindTexture(GL_TEXTURE_2D, 0);

	glActiveTexture(GL_TEXTURE0);
	rb_check_error_simple();
	

}

-(void)generateColor {
	
	glClearColor(1.0, 0.0, 0.0, 1.0);		
	glBindFramebuffer(GL_FRAMEBUFFER, fbo);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTextureID, 0);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
	
	glDisable(GL_DEPTH_TEST);
	glDepthMask(GL_FALSE);
	glClear(GL_COLOR_BUFFER_BIT);
	
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, diffuseTextureID);
	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, filteredAmbientTextureID);
	glActiveTexture(GL_TEXTURE2);
	glBindTexture(GL_TEXTURE_2D, albedoTextureID);

	[mixShader useProgram];
	int i = [mixShader getLocationForAttribute:@"position_in"];
	glUniform1i([mixShader locationForUniform:@"diffuseSampler"], 0);	
	glUniform1i([mixShader locationForUniform:@"ambientSampler"], 1);	
	glUniform1i([mixShader locationForUniform:@"albedoSampler"], 2);	

	glUniform1f([mixShader locationForUniform:@"amt1"], 1.0f);	
	glUniform1f([mixShader locationForUniform:@"amt2"], 1.0f);	

	rb_check_error_simple();
	
	[self drawSquare: i];
	
	glEnable(GL_DEPTH_TEST);
	glDepthMask(GL_TRUE);
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, 0);
	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, 0);
	glActiveTexture(GL_TEXTURE2);
	glBindTexture(GL_TEXTURE_2D, 0);
	glActiveTexture(GL_TEXTURE0);
	rb_check_error_simple();
	
}

-(void)finalBlit {
	
	glBindFramebuffer(GL_FRAMEBUFFER, 0);
	
	glDisable(GL_DEPTH_TEST);
	glDepthMask(GL_FALSE);
	//glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	
	glActiveTexture(GL_TEXTURE0);
	
	NSString *modeString;
	GLint blitTexture;
	switch( finalBlitMode ) {
		case kFinalBlitModeColor: 
			blitTexture = colorTextureID;
			modeString = @"Color";
			break;
		case kFinalBlitModeNormal:
			blitTexture = normalTextureID;
			modeString = @"Normal";
			break;
		case kFinalBlitModeDepth: 
			blitTexture = depthTextureID;
			modeString = @"Depth";
			break;
		case kFinalBlitModeDiffuse: 
			blitTexture = diffuseTextureID;
			modeString = @"Diffuse";
			break;
		case kFinalBlitModeAmbient: 
			blitTexture = ambientTextureID;
			modeString = @"Ambient";
			break;
		case kFinalBlitModeAmbientFiltered: 
			blitTexture = filteredAmbientTextureID;
			modeString = @"Ambient Filtered";
			break;			
		default: 
			blitTexture = 0; 
			modeString = @"???";
			break;
	}	
	
	glBindTexture(GL_TEXTURE_2D, blitTexture);
	
	[gammaCorrectShader useProgram];
	
	
	int i = [gammaCorrectShader getLocationForAttribute:@"position_in"];
	glUniform1i([gammaCorrectShader locationForUniform:@"sampler"], 0);	
	glUniform1i([gammaCorrectShader locationForUniform:@"alphaOnly"], 0);	

	[self drawSquare: i];
	
	glBindTexture(GL_TEXTURE_2D, 0);
	
	[self drawHUD: modeString];
	
	glEnable(GL_DEPTH_TEST);
	glDepthMask(GL_TRUE);
	
	rb_check_error_simple();
		
	
}

-(void)drawRect:(NSRect)dirtyRect {
			
	[self.openGLContext makeCurrentContext];
	
	/* compute time delta */
	struct timeval currentTime;
	gettimeofday(&currentTime, NULL);
	double timeDiff = (double)( currentTime.tv_usec - lastTime.tv_usec ) * (double)(1.0 / 1000000.0) + (double)( currentTime.tv_sec - lastTime.tv_sec );
	if ( timeDiff < 0.0 ) timeDiff = 0.0;
	if ( timeDiff > 1.0 ) timeDiff = 1.0;
	lastTime = currentTime;
	/* ------------------- */
	
	if (!paused) {
		rot += 0.2 * timeDiff;
	}
		
	fovyInDegrees = 35.0f;
	aspect = (float)viewportWidth / (float)viewportHeight;
	zNear = 2.0f;
	zFar  = 100.0f;
	top = zNear * tanf(fovyInDegrees * M_PI / 360.0);
	right = top * aspect;
    bottom = -top;
    left = -top * aspect;
		
	//mat4 modelview = [self getModelview];
	
	[self drawPass: kPassGBuffer];
	[self drawPass: kPassShade];	// generate the color texture (6ms)
	[self generateAmbient];
	[self filterAmbient];
	[self generateColor];
	[self finalBlit];				// blit the color texture to the screen and do any final effects (2ms)
	
	[self.openGLContext flushBuffer];
	
	count++;

}

- (void)drawHUD:(NSString *)modeString {

	[basicShader useProgram];
	
	glEnable(GL_BLEND);
	glDisable(GL_DEPTH_TEST);
	glDepthMask(GL_FALSE);
	glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
	
	mat4 projection = rb_mat4_ortho(0, self.bounds.size.width, self.bounds.size.height, 0, -10, 10);
	
	mat4 modelview  = rb_mat4_translate(make_vec3(10, 10, 0));
	mat4 modelviewProjection = rb_mat4_mul(projection, modelview);
	
	glUniformMatrix4fv( [basicShader locationForUniform:@"modelViewProjectionMatrix"], 1, 0, modelviewProjection.entries );
	glUniform1i([basicShader locationForUniform:@"sampler"], 0);
	glUniform4f([basicShader locationForUniform:@"alpha"], 1.0f, 1.0f, 1.0f, 1.0f);
	
	rb_check_error_simple();
	
	[GLFont mapVerticesToAttributeLocation: [basicShader getLocationForAttribute:@"position_in"] ];
	[GLFont mapTexCoordsToAttributeLocation: [basicShader getLocationForAttribute:@"uv_in"] ];	
	
	rb_check_error_simple();

	[font drawString:[NSString stringWithFormat:@"Frames per second: %d", fps]];
	
	modelview  = rb_mat4_translate(make_vec3(10, 30, 0));
	modelviewProjection = rb_mat4_mul(projection, modelview);
	glUniformMatrix4fv( [basicShader locationForUniform:@"modelViewProjectionMatrix"], 1, 0, modelviewProjection.entries );
	[font drawString:[NSString stringWithFormat:@"Mode: '%@'", modeString]];

	rb_check_error_simple();
	
	glDisable(GL_BLEND);
	
	float vSize = 1.0f / aspect * 128;
	float hSize = 128;
	float margin = 4;
	
	glActiveTexture(GL_TEXTURE0);
	
	glBindTexture(GL_TEXTURE_2D, depthTextureID);
	[self visualizeColorWithImageSize: CGSizeMake(viewportWidth, viewportHeight) dstRect: CGRectMake(margin, margin, hSize, vSize )];
	glBindTexture(GL_TEXTURE_2D, normalTextureID);
	[self visualizeColorWithImageSize: CGSizeMake(viewportWidth, viewportHeight) dstRect: CGRectMake(2 * margin + 1 * hSize, margin, hSize, vSize )];
	glBindTexture(GL_TEXTURE_2D, diffuseTextureID);
	[self visualizeColorWithImageSize: CGSizeMake(viewportWidth, viewportHeight) dstRect: CGRectMake(3 * margin + 2 * hSize, margin, hSize, vSize )];
	glBindTexture(GL_TEXTURE_2D, ambientTextureID);
	[self visualizeColorWithImageSize: CGSizeMake(viewportWidth, viewportHeight) dstRect: CGRectMake(4 * margin + 3 * hSize, margin, hSize, vSize )];
	glBindTexture(GL_TEXTURE_2D, filteredAmbientTextureID);
	[self visualizeColorWithImageSize: CGSizeMake(viewportWidth, viewportHeight) dstRect: CGRectMake(5 * margin + 4 * hSize, margin, hSize, vSize )];
	glBindTexture(GL_TEXTURE_2D, colorTextureID);
	[self visualizeColorWithImageSize: CGSizeMake(viewportWidth, viewportHeight) dstRect: CGRectMake(6 * margin + 5 * hSize, margin, hSize, vSize )];

	glDepthMask(GL_TRUE);
	glEnable(GL_DEPTH_TEST);
	
}

-(GLuint)generateFullscreenTextureWithInternalFormat:(GLint)internalFormat format:(GLenum)format type:(GLenum)type {

	GLuint descriptor;
	glGenTextures(1, &descriptor);	
	glBindTexture(GL_TEXTURE_2D, descriptor);
	glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, viewportWidth, viewportHeight, 0, format, type, NULL);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

	rb_check_error_simple();
	
	return descriptor;
	
}

- (void)checkFramebufferStatus {
	GLuint status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
	if ( status != GL_FRAMEBUFFER_COMPLETE ) {
		NSLog(@"Incomplete framebuffer (color) %s", rbunny_framebuffer_status_to_string(status) );
	}	
	rb_check_error_simple();
}


- (void)rebuildFramebufferObjects {
	
	glFinish();
			
	if ( colorTextureID != 0 ) {
		glDeleteTextures(1, &colorTextureID);
	}
	if ( normalTextureID != 0 ) {
		glDeleteTextures(1, &normalTextureID);
	}
	if ( depthTextureID != 0 ) {
		glDeleteTextures(1, &depthTextureID);
	}	
	if ( diffuseTextureID != 0) {
		glDeleteTextures(1, &diffuseTextureID);
	}
	if ( ambientTextureID != 0 ) {
		glDeleteTextures(1, &ambientTextureID);
	}
	if ( albedoTextureID != 0 ) {
		glDeleteTextures(1, &albedoTextureID);
	}
	
	if ( fbo == 0 ) {
		glGenFramebuffers(1, &fbo);
	}
		
	depthTextureID				= [self generateFullscreenTextureWithInternalFormat: GL_DEPTH_COMPONENT24 format: GL_DEPTH_COMPONENT type: GL_UNSIGNED_INT];
	diffuseTextureID			= [self generateFullscreenTextureWithInternalFormat:GL_RGBA16F_ARB format:GL_RGBA type:GL_HALF_APPLE];
	ambientTextureID			= [self generateFullscreenTextureWithInternalFormat:GL_RGBA16F_ARB format:GL_RGBA type:GL_HALF_APPLE];
	colorTextureID				= [self generateFullscreenTextureWithInternalFormat: GL_RGBA16F_ARB format: GL_RGBA type: GL_HALF_APPLE];
	normalTextureID				= [self generateFullscreenTextureWithInternalFormat: GL_RGBA16F_ARB format: GL_RGBA type: GL_HALF_APPLE];	
	albedoTextureID				= [self generateFullscreenTextureWithInternalFormat: GL_RGBA16F_ARB format: GL_RGBA type: GL_HALF_APPLE];
	filteredAmbientTextureID	= [self generateFullscreenTextureWithInternalFormat: GL_RGBA16F_ARB format: GL_RGBA type: GL_HALF_APPLE];

	glBindFramebuffer(GL_FRAMEBUFFER, fbo);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
	rb_check_error_simple();
	glActiveTexture(GL_TEXTURE0);

	// check that depth + normal works
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTextureID, 0);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, normalTextureID, 0);
	rb_check_error_simple();
	[self checkFramebufferStatus];
	
	// check that depth + diffuse works
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTextureID, 0);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, diffuseTextureID, 0);
	rb_check_error_simple();
	[self checkFramebufferStatus];
	
	// check that SSDO works (no depth)
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, ambientTextureID, 0);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
	rb_check_error_simple();
	[self checkFramebufferStatus];
	
	// check that color works (no depth)
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTextureID, 0);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
	rb_check_error_simple();
	[self checkFramebufferStatus];
	
	[self generateSampleDirections];
	
	glBindFramebuffer(GL_FRAMEBUFFER, 0);
	glFinish();
	
}

- (void)prepareOpenGL {
		
	NSLog(@"Prepare OpenGL");
	
	[self.openGLContext makeCurrentContext];
	
	[self loadShaders];
		
	model11 = [[IndexedModel alloc] initWithFile: [[NSBundle mainBundle] pathForResource:@"monkey" ofType:@"obj"] ];
	[model11 mapBuffer: IMODEL_BUFFER_VERTEX			toAttributeLocation: [tangentShader getLocationForAttribute:@"position"]];
	[model11 mapBuffer: IMODEL_BUFFER_UV				toAttributeLocation: [tangentShader getLocationForAttribute:@"uv"]];
	[model11 mapBuffer: IMODEL_BUFFER_NORMAL			toAttributeLocation: [tangentShader getLocationForAttribute:@"normal"]];
	[model11 mapBuffer: IMODEL_BUFFER_TANGENT		toAttributeLocation: [tangentShader getLocationForAttribute:@"tangent"]];
	[model11 createVertexArray];
	
	model21 = [[IndexedModel alloc] initWithFile: [[NSBundle mainBundle] pathForResource:@"plane" ofType:@"obj"] ];
	[model21 mapBuffer: IMODEL_BUFFER_VERTEX			toAttributeLocation: [tangentShader getLocationForAttribute:@"position"]];
	[model21 mapBuffer: IMODEL_BUFFER_UV				toAttributeLocation: [tangentShader getLocationForAttribute:@"uv"]];
	[model21 mapBuffer: IMODEL_BUFFER_NORMAL			toAttributeLocation: [tangentShader getLocationForAttribute:@"normal"]];
	[model21 mapBuffer: IMODEL_BUFFER_TANGENT		toAttributeLocation: [tangentShader getLocationForAttribute:@"tangent"]];
	[model21 createVertexArray];
	
	
	model12 = [[IndexedModel alloc] initWithFile: [[NSBundle mainBundle] pathForResource:@"monkey" ofType:@"obj"] ];
	[model12 mapBuffer: IMODEL_BUFFER_VERTEX			toAttributeLocation: [lightBufferShader getLocationForAttribute:@"position"]];
	[model12 mapBuffer: IMODEL_BUFFER_UV				toAttributeLocation: [lightBufferShader getLocationForAttribute:@"uv"]];
	[model12 createVertexArray];
	
	model22 = [[IndexedModel alloc] initWithFile: [[NSBundle mainBundle] pathForResource:@"plane" ofType:@"obj"] ];
	[model22 mapBuffer: IMODEL_BUFFER_VERTEX			toAttributeLocation: [lightBufferShader getLocationForAttribute:@"position"]];
	[model22 mapBuffer: IMODEL_BUFFER_UV				toAttributeLocation: [lightBufferShader getLocationForAttribute:@"uv"]];
	[model22 createVertexArray];
	
	
	rb_check_error_simple();
	
	lightmap = [[GLTexture alloc] initWithFile: [[NSBundle mainBundle] pathForResource:@"lightmap" ofType:@"jpg"]
										 flags: kGLTextureFilterModeLinear|kGLTextureWrapModeRepeat];

	
	texture1 = [[GLTexture alloc] initWithFile: [[NSBundle mainBundle] pathForResource:@"ash_uvgrid03" ofType:@"jpg"]
										flags: kGLTextureFilterModeLinear|kGLTextureWrapModeRepeat|kGLTextureGenerateMipMaps];
    	
	texture2 = [[GLTexture alloc] initWithFile: [[NSBundle mainBundle] pathForResource:@"gray" ofType:@"jpg"]
										 flags: kGLTextureFilterModeLinear|kGLTextureWrapModeRepeat|kGLTextureGenerateMipMaps];

			
	
	GLubyte whiteColor[4] = { 255, 255, 255, 255 };
	GLubyte blackColor[4] = { 0, 0, 0, 255 };
	
	font = [[GLFont alloc] initWithFontName:[[NSFont systemFontOfSize: 16] fontName] size: 16 color: whiteColor stroke: 1.0f strokeColor: blackColor];
	
	glClearDepth(1.0f);
    glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
	glEnable(GL_CULL_FACE);	
		
	rb_check_error_simple();
		
	viewportWidth = self.bounds.size.width;
	viewportHeight = self.bounds.size.height;
	
	colorTextureID = 0;
	depthTextureID = 0;
	normalTextureID = 0;
	
	[self rebuildFramebufferObjects];

}

-(void)reshape {
	
	[self.openGLContext makeCurrentContext];
	glFinish();

	viewportWidth = self.bounds.size.width;
	viewportHeight = self.bounds.size.height;
	glViewport(0, 0, viewportWidth, viewportHeight);
			
	[self rebuildFramebufferObjects];
	
	[self setNeedsDisplay: YES];
	
}

@end
